package com.jarveis.frame.lang;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

/**
 * String工具类
 * 
 * @author liuguojun
 */
public class StringUtil {

	private static final Logger logger = Logger.getLogger(StringUtil.class);

	/**
	 * 验证字符串是否为null或空字符, 如果为空返回true否则返回false
	 * 
	 * <pre>
	 * StringUtil.isEmpty(null) = true;
	 * StringUtil.isEmpty(&quot;&quot;) = true;
	 * StringUtil.isEmpty(&quot;  &quot;) = false;
	 * StringUtil.isEmpty(&quot;a&quot;) = false;
	 * </pre>
	 * 
	 * @param str
	 *            验证字符串
	 * @return boolean
	 */
	public static boolean isEmpty(String str) {
		return (str == null) || (str.length() == 0);
	}

	/**
	 * 验证字符串是否为null或空字符或空白字符, 如果为空返回false否则返回true
	 * 
	 * <pre>
	 * StringUtil.isBlank(null) = true;
	 * StringUtil.isBlank(&quot;&quot;) = true;
	 * StringUtil.isBlank(&quot;  &quot;) = true;
	 * StringUtil.isBlank(&quot;\t \n \f \r&quot;) = true
	 * StringUtils.isBlank(&quot;\b&quot;) = false
	 * StringUtil.isBlank(&quot;a&quot;) = false;
	 * </pre>
	 * 
	 * @param str
	 *            验证字符串
	 * @return boolean
	 */
	public static boolean isBlank(String str) {
		int strLen;
		if ((str == null) || ((strLen = str.length()) == 0)) {
			return true;
		}
		for (int i = 0; i < strLen; i++) {
			if (!Character.isWhitespace(str.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 验证字符串是否只包含数字
	 * 
	 * <pre>
	 * StringUtil.isDigit(&quot;12ab47&quot;) = false;
	 * StringUtil.isDigit(&quot;1247&quot;) = true;
	 * </pre>
	 * 
	 * @param str
	 *            验证字符串
	 * @return boolean
	 */
	public static boolean isDigit(String str) {
		for (int i = str.length(); --i >= 0;) {
			if (!Character.isDigit(str.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 验证字符串是否只包含字母
	 * 
	 * <pre>
	 * StringUtil.isLetter(&quot;12ab47&quot;) = false;
	 * StringUtil.isLetter(&quot;ab&quot;) = true;
	 * </pre>
	 * 
	 * @param str
	 *            验证字符串
	 * @return boolean
	 */
	public static boolean isLetter(String str) {
		for (int i = str.length(); --i >= 0;) {
			if (!Character.isLetter(str.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 验证字符串是否只包含中文字符
	 * 
	 * <pre>
	 * StringUtil.isChinese(&quot;12ab47你好&quot;) = false;
	 * StringUtil.isChinese(&quot;你好&quot;) = true;
	 * </pre>
	 * 
	 * @param str
	 * @return boolean
	 */
	public static boolean isChinese(String str) {
		String[] arr = find(str, "[\u4e00-\u9fa5]+");
		return (join(arr, "").length() == str.length());
	}

	/**
	 * 去除字符串两边的空格，并返回字符串，否则返回为null
	 * 
	 * <pre>
	 * StringUtil.trim(&quot;&quot;) = null;
	 * StringUtil.trim(&quot; aaa &quot;) = &quot;aaa&quot;;
	 * </pre>
	 * 
	 * @param str
	 *            操作字符串
	 * @return String
	 */
	public static String trim(String str) {
		return isEmpty(str) ? null : str.trim();
	}

	/**
	 * 对原始字符串进行编码转换，如果失败，返回原始的字符串
	 * 
	 * @param s
	 *            原始字符串
	 * @param srcEncoding
	 *            源编码方式
	 * @param destEncoding
	 *            目标编码方式
	 * @return 转换编码后的字符串，失败返回原始字符串
	 */
	public static String getString(String s, String srcEncoding,
			String destEncoding) {
		try {
			return new String(s.getBytes(srcEncoding), destEncoding);
		} catch (UnsupportedEncodingException e) {
			return s;
		}
	}

	/**
	 * 根据某种编码方式将字节数组转换成字符串
	 * 
	 * @param b
	 *            字节数组
	 * @param encoding
	 *            编码方式
	 * @return 如果encoding不支持，返回一个缺省编码的字符串
	 */
	public static String getString(byte[] b, String encoding) {
		try {
			return new String(b, encoding);
		} catch (UnsupportedEncodingException e) {
			return new String(b);
		}
	}

	/**
	 * 根据某种编码方式将字节数组转换成字符串
	 * 
	 * @param b
	 *            字节数组
	 * @param offset
	 *            要转换的起始位置
	 * @param len
	 *            要转换的长度
	 * @param encoding
	 *            编码方式
	 * @return 如果encoding不支持，返回一个缺省编码的字符串
	 */
	public static String getString(byte[] b, int offset, int len,
			String encoding) {
		try {
			return new String(b, offset, len, encoding);
		} catch (UnsupportedEncodingException e) {
			return new String(b, offset, len);
		}
	}

	/**
	 * 在内容中查找与给定的正则表达式相匹配的字符串
	 * 
	 * <pre>
	 * StringUtil.find("北风2-3级", "\\d+") = {"2", "3"};
	 * </pre>
	 * 
	 * @param content
	 *            要查找内容
	 * @param regex
	 *            正则表达式
	 * @return String[]
	 */
	public static String[] find(String content, String regex) {
		ArrayList<String> array = new ArrayList<String>();
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(content);
		while (matcher.find()) {
			array.add(matcher.group());
		}
		array.trimToSize();

		return array.toArray(new String[array.size()]);
	}

	/**
	 * 替换内容中与给定的正则表达式相匹配的所有字符串
	 * 
	 * <pre>
	 * StringUtil.replaceAll(&quot;南风3级转北风2-3级&quot;, &quot;\\d+-*\\d*级&quot;, &quot;&quot;) = &quot;南风转北风&quot;;
	 * </pre>
	 * 
	 * @param content
	 *            要查找内容
	 * @param regex
	 *            正则表达式
	 * @param newStr
	 *            替换的新字符串
	 * @return String
	 */
	public static String replaceAll(String content, String regex, String newStr) {
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(content);
		return matcher.replaceAll(newStr);
	}

	/**
	 * 替换内容中与给定的正则表达式相匹配的第一个字符串
	 * 
	 * <pre>
	 * StringUtil.replaceAll(&quot;南风3级转北风2-3级&quot;, &quot;\\d+-*\\d*级&quot;, &quot;&quot;) = &quot;南风转北风2-3级&quot;;
	 * </pre>
	 * 
	 * @param content
	 *            要查找内容
	 * @param regex
	 *            正则表达式
	 * @param newStr
	 *            替换的新字符串
	 * @return String
	 */
	public static String replaceFirst(String content, String regex,
			String newStr) {
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(content);
		return matcher.replaceFirst(newStr);
	}

	/**
	 * 获取全局的唯一标识符
	 * 
	 * <pre>
	 * StringUtil.getUUID() = 2a3ac247cc1245b7bfa147718357520f;
	 * </pre>
	 * 
	 * @return String
	 */
	public static String getUUID() {
		String primaryKey = UUID.randomUUID().toString();
		return primaryKey.replaceAll("-", "");
	}

	/**
	 * 更改字符串,使用其首字母大写
	 * 
	 * <pre>
	 * StringUtil.initialUpperCase(&quot;name&quot;) = Name;
	 * </pre>
	 * 
	 * @param str
	 *            字符串(首字符必须是字母)
	 * @return String 返回更改后的字符串
	 */
	public static String initialUpperCase(String str) {
		final StringBuffer s = new StringBuffer();
		s.append(str.substring(0, 1).toUpperCase());
		s.append(str.substring(1));
		return s.toString();
	}

	/**
	 * 还原html的特殊字符
	 * 
	 * @param str
	 * @return String
	 */
	public static String revertHtmlChar(String str) {
		str = str.replace("＆＃63;", "?");
		str = str.replace("＆＃38;", "&");
		str = str.replace("＆＃60;", "<");
		str = str.replace("＆＃62;", ">");
		str = str.replace("＆＃34;", "\"");
		str = str.replace("＆＃39;", "'");

		return str;
	}

	/**
	 * 连接字符数组为字符串
	 * 
	 * <pre>
	 * StringUtil.join(["aaa", "bbb", "ccc"], ",") = "aaa,bbb,ccc";
	 * StringUtil.join(["aaa", "bbb", "ccc"], "-") = "aaa-bbb-ccc";
	 * </pre>
	 * 
	 * @param arr
	 *            字符数组
	 * @param delimit
	 *            分隔符
	 * @return String
	 */
	public static String join(String[] arr, String delimit) {
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < arr.length; i++) {
			sb.append(arr[i]);
			if (i + 1 == arr.length) {
				sb.append(delimit);
			}
		}
		return sb.toString();
	}

	/**
	 * 连接字符数组为字符串
	 * 
	 * <pre>
	 * StringUtil.join(["aaa", "bbb", "ccc"]) = "aaa,bbb,ccc";
	 * StringUtil.join(["1", "2", "3") = "1,2,3";
	 * </pre>
	 * 
	 * @param arr
	 *            字符数组
	 * @param delimit
	 *            分隔符
	 * @return String
	 */
	public static String join(String[] arr) {
		return join(arr, ",");
	}

	/**
	 * 分解字符串为字符数组
	 * 
	 * <pre>
	 * StringUtil.split("aaa,bbb,ccc", ",") = ["aaa", "bbb", "ccc"];
	 * </pre>
	 * 
	 * @param str
	 *            字符串
	 * @param delimit
	 *            分隔符
	 * @return String[]
	 */
	public static String[] split(String str, String delimit) {
		final StringTokenizer commaToker = new StringTokenizer(str, delimit);
		final String[] strArr = new String[commaToker.countTokens()];
		for (int i = 0; commaToker.hasMoreTokens(); i++) {
			strArr[i] = commaToker.nextToken();
		}
		return strArr;
	}

	/**
	 * 分解字符串为字符数组
	 * 
	 * <pre>
	 * StringUtil.split("aaa,bbb,ccc") = ["aaa", "bbb", "ccc"];
	 * </pre>
	 * 
	 * @param str
	 *            字符串
	 * @param delimit
	 *            分隔符
	 * @return String[]
	 */
	public static String[] split(String str) {
		return split(str, ",");
	}

	/**
	 * 获取指定长度生成的随机密码
	 * 
	 * <pre>
	 * StringUtil.random(4) = 0yb8;
	 * StringUtil.random(6) = ac23ep;
	 * </pre>
	 * 
	 * @param length
	 * @return String
	 */
	public static String random(int length) {
		final int maxNum = 36;
		int i, count = 0;
		final char[] str = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
				'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
				'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
				'8', '9' };
		final StringBuffer pwd = new StringBuffer("");
		final Random r = new Random();
		while (count < length) {
			i = Math.abs(r.nextInt(maxNum));
			if (i >= 0 && i < str.length) {
				pwd.append(str[i]);
				count++;
			}
		}
		return pwd.toString();
	}

	/**
	 * 对给定字符进行 URL编码
	 * 
	 * <pre>
	 * StringUtil.encode("中国") = "%E4%B8%AD%E5%9B%BD";
	 * StringUtil.encode(""); = "";
	 * </pre>
	 * 
	 * @param str
	 * @return String
	 */
	public static String encode(String str) {
		String temp = "";
		try {
			if (!isEmpty(str)) {
				temp = URLEncoder.encode(str, CharacterUtil.UTF8);
			}
		} catch (UnsupportedEncodingException ex) {
			logger.error(ex.getMessage(), ex);
		}
		return temp;
	}

	/**
	 * 对给定字符进行 URL解码
	 * 
	 * <pre>
	 * StringUtil.decode(&quot;%E4%B8%AD%E5%9B%BD&quot;) = &quot;中国&quot;;
	 * StringUtil.decode(&quot;&quot;) = &quot;&quot;;
	 * </pre>
	 * 
	 * @param str
	 * @return String
	 */
	public static String decode(String str) {
		String temp = "";
		try {
			if (!isEmpty(str)) {
				temp = URLDecoder.decode(str, CharacterUtil.UTF8);
			}
		} catch (UnsupportedEncodingException ex) {
			logger.error(ex.getMessage(), ex);
		}
		return temp;
	}

	/**
	 * 获取流水号
	 * 
	 * @param pattern
	 * @param digit
	 * @return String
	 * @throws SQLException
	 */
	public static String getJournalNumber(String pattern, int digit,
			Long lastCode) throws SQLException {
		String str = (lastCode != null) ? String.valueOf(lastCode.longValue())
				: null;
		return getJournalNumber(null, pattern, null, digit, str);
	}

	/**
	 * 获取流水号
	 * 
	 * @param prefix
	 *            前缀
	 * @param pattern
	 *            日期格式
	 * @param suffix
	 *            后缀
	 * @param digit
	 *            流水号位数
	 * @return String
	 */
	public static String getJournalNumber(String prefix, String pattern,
			String suffix, int digit) {
		final StringBuffer seqNo = new StringBuffer();
		if (!isEmpty(prefix)) {
			seqNo.append(prefix);
		}
		seqNo.append(DateUtil.getDateStr(DateUtil.getDate(), pattern));
		seqNo.append(fillZero("1", digit));
		if (!isEmpty(suffix)) {
			seqNo.append(suffix);
		}
		return seqNo.toString();
	}

	/**
	 * 获取流水号
	 * 
	 * @param prefix
	 * @param pattern
	 * @param suffix
	 * @param digit
	 * @param lastCode
	 * @return String
	 * @throws SQLException
	 */
	public static String getJournalNumber(String prefix, String pattern,
			String suffix, int digit, String lastCode) throws SQLException {
		String newSeqNo = getJournalNumber(prefix, pattern, suffix, digit);

		// 如果lastCode为空说明没有生成过流水号
		if (lastCode == null) {
			return newSeqNo;
		}

		if (newSeqNo.length() != lastCode.length()) {
			// 跟据规则生成的流水号和以前的流水号的长度不一的话，
			// 说明生成流水号的规则发生变话，则返回新成的流水号
			return newSeqNo;
		}

		String tempStr = lastCode;
		if (!isEmpty(prefix)) {
			String tempPrefix = lastCode.substring(0, prefix.length());
			if (!tempPrefix.equals(prefix)) {
				// 如果以前流水号的前缀跟给定的前缀规则不一的话
				// 说明生成流水号的规则发生变话，则返回新成的流水号
				return newSeqNo;
			}
			tempStr = tempStr.substring(prefix.length());
		}

		if (!isEmpty(suffix)) {
			String tempSuffix = lastCode.substring(lastCode.length()
					- suffix.length());
			if (!tempSuffix.equals(suffix)) {
				// 如果以前流水号的后缀跟给定的后缀规则不一的话
				// 说明生成流水号的规则发生变话，则返回新成的流水号
				return newSeqNo;
			}
			tempStr = tempStr.substring(0, tempStr.length() - suffix.length());
		}

		if (tempStr.length() != pattern.length() + digit) {
			// 如果以前流水号的时间+流水号部分跟给定的日期模式的长度+流水号位数不一的话
			// 说明生成流水号的规则发生变话，则返回新成的流水号
			return newSeqNo;
		}

		String dateStr = tempStr.substring(0, tempStr.length() - digit);
		if (DateUtil.getDate(dateStr, pattern) == null) {
			// 如果以前流水号的时间部分跟给定的日期模式不能生成日期对象的话
			// 说明生成流水号的规则发生变话，则返回新成的流水号
			return newSeqNo;
		}

		if (!dateStr.equals(DateUtil.getDateStr(DateUtil.getDate(), pattern))) {
			// 如果以前流水号的时间部分跟系统的时间部分不同的话
			// 需生成新的流水号
			return newSeqNo;
		}

		String digitStr = tempStr.substring(dateStr.length());
		String newDigitStr = String.valueOf(Long.parseLong(digitStr) + 1);
		if (newDigitStr.length() <= digit) {
			// 跟据老的序列号生成新的序列号
			StringBuffer seqNo = new StringBuffer();
			if (!isEmpty(prefix)) {
				seqNo.append(prefix);
			}
			seqNo.append(DateUtil.getDateStr(DateUtil.getDate(), pattern));
			seqNo.append(fillZero(newDigitStr, digit));
			if (!isEmpty(suffix)) {
				seqNo.append(suffix);
			}
			return seqNo.toString();
		} else {
			throw new SQLException(
					"generating seqno failed for digit length not enough!");
		}
	}

	/**
	 * 填充零
	 * 
	 * <pre>
	 * String num = StringUtil.fillZero(&quot;1&quot;, &quot;3&quot;); // num = &quot;001&quot;
	 * </pre>
	 * 
	 * @param no
	 *            数字
	 * @param digit
	 *            位数
	 * @return String 填充后的数字
	 */
	public static String fillZero(String no, int digit) {
		StringBuffer num = new StringBuffer();

		if (isEmpty(no)) {
			no = "1";
		}
		for (int i = 0; i < digit - no.length(); i++) {
			num.append(0);
		}
		num.append(no);

		return num.toString();
	}

	/**
	 * 用途：检查输入手机号是否有效的 输入：
	 * 
	 * @param s
	 *            要判断的字符串
	 * @return 如果通过验证返回true,否则返回false
	 */
	public static boolean isMobile(String s) {
		return s.matches("[1][0-9][0-9]{9}");

	}

	/**
	 * 用途：检查输入邮箱地址是否有效的 输入：
	 * 
	 * @param s
	 *            要判断的字符串
	 * @return 如果通过验证返回true,否则返回false
	 */
	public static boolean isEmail(String s) {
		return s.matches("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$");
	}

	/**
	 * 随机取x个数字
	 * 
	 * @return
	 */
	public static String getRandomNum(int x) {
		String randomNum = "";
		try {
			Random rd = new Random(); // 创建随机对象
			int rdGet = 0; // 取得随机数
			char num1 = '0';
			do {
				rdGet = Math.abs(rd.nextInt()) % 10 + 48; // 产生48到57的随机数(0-9的键位值)
				// rdGet = Math.abs(rd.nextInt()) % 26 + 97; //
				// 产生97到122的随机数(a-z的键位值)
				num1 = (char) rdGet;
				randomNum += Character.toString(num1);
			} while (randomNum.length() < x);// 假如长度小于4
		} catch (Exception e) {
			logger.error("获取x个随机取数字异常", e);
		}
		return randomNum;
	}

	/**
	 * 用途：检查输入字符串是由4至16位可包含(字母、数字、.和下划线) 输入： s：字符串 返回： 如果通过验证返回true,否则返回false
	 */
	public static boolean isStartLetter_Length4_16(String s) {
		return s.matches("^([0-9a-zA-Z\u4e00-\u9fa5]|[._#]){4,16}$");
	}

	/**
	 * 用途：检查输入字符串必须在6位及以上,20位以下（含6位和15位）且只能由字母或数字组成。
	 * 
	 * 输入： s：字符串 返回： 如果通过验证返回true,否则返回false
	 * 
	 */
	public static boolean is6NumOrLetter(String s) { // 判断是否是数字或字母
		return s.matches("^([a-zA-Z0-9]){6,15}$");
	}

	/**
	 * Levenshtein 距离，又称编辑距离，指的是两个字符串之间，由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符
	 * ，插入一个字符，删除一个字符。
	 * 
	 * 例如把 kitten 转换为 sitting： 
	 * sitten （k→s） 
	 * sittin （e→i） 
	 * sitting （→g） 
	 *  
	 * levenshtein() 函数给每个操作（替换、插入和删除）相同的权重。不过，您可以通过设置可选的 insert、replace、delete 参数，来定义每个操作的代价。
	 * 
	 * @param s1
	 * @param s2
	 * @return
	 */
	public static double levenshtein(String s1, String s2) {
		int matrix[][];
		int n = s1.length();
		int m = s2.length();
		int i;
		int j;
		char c1;
		char c2;
		int temp;
		if (n == 0)
			return m;
		if (m == 0)
			return n;

		matrix = new int[n + 1][m + 1];
		for (i = 0; i <= n; i++) {
			matrix[i][0] = i;
		}

		for (j = 0; j <= m; j++) {
			matrix[0][j] = j;
		}

		for (i = 1; i <= n; i++) {
			c1 = s1.charAt(i - 1);

			for (j = 1; j <= m; j++) {
				c2 = s2.charAt(j - 1);
				if (c1 == c2)
					temp = 0;
				else
					temp = 1;

				matrix[i][j] = min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1,
						matrix[i - 1][j - 1] + temp);
			}
		}

		return 1 - (double) matrix[n][m] / Math.max(s1.length(), s2.length());
	}

	private static int min(int one, int two, int three) {
		int min = one;
		if (two < min)
			min = two;
		if (three < min)
			min = three;
		return min;
	}
}
